package com.voltmaon.shortlink.service;

import java.net.URI;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import com.voltmaon.shortlink.util.ResponseContent;
import org.springframework.data.redis.core.BoundValueOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Service;
import com.voltmaon.shortlink.config.BaseConfig;
import com.voltmaon.shortlink.jpa.ShortLink;
import com.voltmaon.shortlink.repo.ShortLinkRepo;
import com.voltmaon.shortlink.util.RedisKeyBuilder;
import com.voltmaon.shortlink.vo.ShortLinkVO;

import cn.hutool.cache.Cache;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RadixUtil;
import cn.hutool.core.util.StrUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;
import reactor.core.publisher.Mono;

/**
 * 短链接
 *
 * @author lfj
 * @date 2021/06/21
 */
@Service
@AllArgsConstructor
@Slf4j
public class ShortLinkService {
	/**
	 * 无效的shortLink id
	 */
	public final static Long INVALID_ID = -1L;
	public final static long DEFAULT_DAYS = 1L;
	private final BaseConfig redirectConfig;
	private final ShortLinkRepo shortLinkRepo;
	private final RedisTemplate<String, Object> redisTemplate;
	public final static String RADIXS_55 = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
	public final Cache<String, ShortLink> shortLinkCache;

	/**
	 * @param response 响应
	 * @param sl       例: xxx.com/DC92X 短链接code: DC92X , s.xxx.com/DC98N 短链接code: DC98N
	 * @param authCode 密码
	 * @return Mono<Void>
	 */
	public Mono<Void> redirect(ServerHttpResponse response, String sl, String authCode) {
		return Mono.fromRunnable(() -> {
			String link;
			String key = RedisKeyBuilder.getShortLinkKey(sl);
			Optional<ShortLink> shortLinkOptional = Optional.ofNullable(shortLinkCache.get(key));
			// cache中有值的情况,直接取值
			if(shortLinkOptional.isPresent()) {
				link = getRedirectLink(response, shortLinkOptional.get(), authCode);
			} else {
				// 若cache中无对应值,则从redis、数据库中按序取值,并存放于cache以及看情况存放在redis中
				BoundValueOperations<String, Object> boundValueOps = redisTemplate.boundValueOps(key);
				Object shortLinkFromRedis = boundValueOps.get();

				// redis无值
				if(Objects.isNull(shortLinkFromRedis)) {

					Optional<ShortLink> shortLinkFromDb = shortLinkRepo.findByShortLink(sl);
					if(shortLinkFromDb.isPresent()) {
						ShortLink shortLink = shortLinkFromDb.get();
						shortLinkCache.put(key, shortLink);
						boundValueOps.set(shortLink, redirectConfig.getDeadline(), TimeUnit.DAYS);
						link = getRedirectLink(response, shortLink, authCode);
					} else {
						// 用户传入无效短链接,也进行缓存占位,防止使用无效短链接多次请求到数据库
						ShortLink invalidLink = new ShortLink();
						shortLinkCache.put(key, invalidLink);
						invalidLink.setId(INVALID_ID);
						boundValueOps.set(invalidLink, DEFAULT_DAYS, TimeUnit.DAYS);
						link = redirectConfig.getExceptionLink();
					}
				} else {
					// redis有值
					ShortLink shortLink = (ShortLink) shortLinkFromRedis;
					shortLinkCache.put(key, shortLink);
					link = getRedirectLink(response, shortLink, authCode);
				}
			}
			// 发出响应并跳转
			response.setStatusCode(HttpStatus.FOUND);
			response.getHeaders()
				.setLocation(URI.create(link));
		});
	}

	public Mono<Void> authRedirect(ServerHttpResponse response, String sl, String authCode) {
		return Mono.fromRunnable(() -> {
			String fullLink = getRedirectLink(response, getShortLinkFromRedis(sl), authCode);
			response.setStatusCode(HttpStatus.FOUND);
			response.getHeaders()
				.setLocation(URI.create(fullLink));
		});
	}

	private ShortLink getShortLinkFromRedis(String sl) {
		return (ShortLink) redisTemplate.opsForValue()
			.get(RedisKeyBuilder.getShortLinkKey(sl));
	}

	private String getRedirectLink(ServerHttpResponse response, ShortLink shortLink, String authCode) {
		if(Objects.nonNull(shortLink.getAuthCode())) {

			// 需要密码保护的短链接,前端没有传递密码则跳转到中间页面
			if(StrUtil.isBlank(authCode)) {
				response.getHeaders()
					.set("shortLink", shortLink.getShortLink());
				response.getHeaders()
					.setOrigin("*");
				return redirectConfig.getTransferLink() + "?short=" + shortLink.getShortLink();
			}

			if(shortLink.getAuthCode()
				.equals(authCode)) {
				return shortLink.getFullLink();
			} else {
				throw new RuntimeException("密码错误");
			}

		}
		if(Objects.nonNull(shortLink.getDeadline()) && shortLink.getDeadline()
			.isBeforeNow()) {
			throw new RuntimeException("链接已过期");
		}
		if(shortLink.getId()
			.equals(INVALID_ID)) {
			throw new RuntimeException("无效的链接");
		}
		return shortLink.getFullLink();
	}

	private ResponseContent getRedirectLink(ShortLink shortLink, String authCode) {
		if(Objects.nonNull(shortLink.getAuthCode())) {
			// 需要密码保护的短链接,前端没有传递密码则异常
			if(StrUtil.isBlank(authCode)) {
				return ResponseContent.error()
					.message("密码不能为空!");
			}
			if(shortLink.getAuthCode()
				.equals(authCode)) {
				return ResponseContent.ok()
					.data("link", shortLink.getFullLink());
			} else {
				return ResponseContent.error()
					.message("密码错误!");
			}
		}
		if(Objects.nonNull(shortLink.getDeadline()) && shortLink.getDeadline()
			.isBeforeNow()) {
			return ResponseContent.error()
				.message("链接已过期!");
		}
		if(shortLink.getId()
			.equals(INVALID_ID)) {
			return ResponseContent.error()
				.message("无效的链接!");
		}
		return ResponseContent.ok()
			.data("link", shortLink.getFullLink());
	}

	@Transactional(rollbackFor = Exception.class)
	public ResponseContent save(ShortLinkVO linkVO) {
		ShortLink shortLink = new ShortLink();
		BeanUtil.copyProperties(linkVO, shortLink, "deadline");
		shortLink.setDeadline(linkVO.getEffectiveDate());
		shortLink.setAuthCode(linkVO.getAuthCode());

		shortLinkRepo.save(shortLink);
		shortLink.setShortLink(RadixUtil.encode(RadixUtil.RADIXS_SHUFFLE_34, shortLink.getId()));
		shortLinkRepo.save(shortLink);

		return ResponseContent.ok()
			.data("shortlink", shortLink);
	}

	public ResponseContent clearRedis() {
		List<ShortLink> all = shortLinkRepo.findAll();
		all.forEach((item) -> {
			String key = RedisKeyBuilder.getShortLinkKey(item.getShortLink());
			redisTemplate.delete(key);
		});
		return ResponseContent.ok()
			.message("成功");
	}

	public static void main(String[] args) {
		Long mainLong = 10000000L;
		for(int i = 0; i < 100; i++) {
			String encode = RadixUtil.encode(RadixUtil.RADIXS_SHUFFLE_34, mainLong);
			System.out.println(encode);
			System.out.println(RadixUtil.decode(RadixUtil.RADIXS_SHUFFLE_34, encode));
			mainLong++;
		}
	}

	// 密码校验
	public ResponseContent verifAuth(ServerHttpResponse response, String shortLink, String auth) {
		String key = RedisKeyBuilder.getShortLinkKey(shortLink);
		BoundValueOperations<String, Object> boundValueOps = redisTemplate.boundValueOps(key);
		Object shortLinkFromRedis = boundValueOps.get();

		ShortLink sl = Optional.ofNullable(shortLinkCache.get(key))
			.orElse((ShortLink) shortLinkFromRedis);
		Optional<ShortLink> sltmp = Optional.ofNullable(sl);

		if(!sltmp.isPresent()) {
			Optional<ShortLink> byShortLink = shortLinkRepo.findByShortLink(shortLink);
			if(!byShortLink.isPresent()) {
				// 存在有人故意进入对应链接放假数据的情况
				return ResponseContent.error()
					.message("请求到了假数据");
			}
			sl = byShortLink.get();
		}
		return getRedirectLink(sl, auth);
	}
}